package cn.lg.soar.common.util;

import cn.lg.soar.common.annotations.DictDefine;
import cn.lg.soar.common.data.dict.IDict;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.common.util.reflect.ReflectUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 枚举工具
 * @author luguoxiang
 * @date 2021/8/17
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public abstract class EnumUtils {

    private final static Map<Class<? extends Enum<?>>, Map<String, Map<Object, String>>> CACHE = new ConcurrentReferenceHashMap<>();

    /**
     * 枚举转map
     * @param enumClass 枚举类型
     * @param value 值方法
     * @param label 名称方法
     * @return
     */
    public static Map<Object, String> toMap(Class<? extends Enum<?>> enumClass, String value, String label) {
        if (IDict.class.isAssignableFrom(enumClass)) {
            // value 和 label 都为空
            if (StringUtil.isBlank(value) && StringUtil.isBlank(label)) {
                return IDict.toMap(getEnumConstants((Class<? extends IDict<Object>>) enumClass));
            }
            // value 和 label 其中一个为空
            if (StringUtil.isBlank(value)) {
                value = "getValue()";
            } else {
                label = "getLabel()";
            }
        } else {
            // 非数据字典接口处理
            value = StringUtil.isBlank(value) ? "ordinal()" : value;
            label = StringUtil.isBlank(label) ? "name()" : label;
        }

        // 获取value
        String finalValueMethod = value;
        Function<Enum<?>, Object> getValue;
        if (value.endsWith("()")) {
            getValue = e -> ReflectUtil.invoke(e, finalValueMethod);
        } else {
            getValue = e -> ReflectUtil.get(e, finalValueMethod);
        }

        // 获取label
        String finalLabelMethod = label;
        Function<Enum<?>, String> getLabel;
        if (label.endsWith("()")) {
            getLabel = e -> ReflectUtil.invoke(e, finalLabelMethod).toString();
        } else {
            getLabel = e -> ReflectUtil.get(e, finalLabelMethod).toString();
        }

        Enum<?>[] enums = getEnumConstants(enumClass);
        Map<Object, String> map = new HashMap<>(enums.length);
        for (Enum<?> x : enums) {
            map.put(getValue.apply(x), getLabel.apply(x));
        }
        return map;
    }

    /**
     * 枚举转map
     * @param enumClass 枚举类型
     * @return
     */
    public static Map<Object, String> toMap(Class<? extends Enum<?>> enumClass) {
        return toMap(enumClass, null, null);
    }

    /**
     * 枚举转map并缓存
     * @param enumClass 枚举类型
     * @param value 值方法
     * @param label 名称方法
     * @return
     */
    public static Map<Object, String> toMapByCache(Class<? extends Enum<?>> enumClass, String value, String label) {
        return CACHE.computeIfAbsent(enumClass, k -> new ConcurrentReferenceHashMap<>(16))
                .computeIfAbsent(
                        (value == null ? "" : value) +":"+ (label == null ? "" : label),
                        k -> toMap(enumClass, value, label));
    }

    /**
     * 枚举转map并缓存
     * @param enumClass 枚举类型
     * @return
     */
    public static Map<Object, String> toMapByCache(Class<? extends Enum<?>> enumClass) {
        return toMapByCache(enumClass, null, null);
    }

    /**
     * 扫描多个枚举，以数组形式返回
     * @param packages
     * @param names
     * @return
     */
    public static Map<String, IDict<?>[]> scanToArrays(String packages, Collection<String> names) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(packages);
        Map<String, IDict<?>[]> maps;
        Predicate<String> predicate;
        if (names.isEmpty()) {
            // 扫描全部
            maps = new HashMap<>(candidateComponents.size());
            predicate = s -> true;
        } else {
            Set<String> set = new HashSet<>(names);
            maps = new HashMap<>(names.size());
            predicate = n -> set.contains(n);
        }
        try {
            // 扫描指定名称的枚举
            for (BeanDefinition candidateComponent : candidateComponents) {
                Class<?> x = Class.forName(candidateComponent.getBeanClassName());
                if (IDict.class.isAssignableFrom(x) && predicate.test(x.getSimpleName())) {
                    maps.put(x.getSimpleName(), (IDict<?>[]) x.getEnumConstants());
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return maps;
    }

    /**
     * 扫描多个枚举，以数组形式返回
     * @param packages
     * @param names
     * @return
     */
    @SafeVarargs
    public static Map<String, IDict<?>[]> scanToArrays(String packages, String...names) {
        return scanToArrays(packages, Arrays.asList(names));
    }

    /**
     * 扫描枚举，以数组形式返回
     * @param packages
     * @param name
     * @return
     */
    public static IDict<?>[] scanToArray(String packages, String name){
        Map<String, IDict<?>[]> map = scanToArrays(packages, name);
        return map.get(name);
    }

    /**
     * 扫描多个枚举，以map形式返回
     * @param packages 包名
     * @param names 类名（非全类名）
     * @return
     */
    public static Map<String, Map<Object, String>> scanToMaps(String packages, Collection<String> names) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(packages);
        Map<String, Map<Object, String>> maps;
        Predicate<String> predicate;
        if (names.isEmpty()) {
            // 扫描全部
            maps = new HashMap<>(candidateComponents.size());
            predicate = s -> true;
        } else {
            Set<String> set = new HashSet<>(names);
            maps = new HashMap<>(names.size());
            predicate = n -> set.contains(n);
        }
        try {
            // 扫描指定名称的枚举
            for (BeanDefinition candidateComponent : candidateComponents) {
                Class<?> x = Class.forName(candidateComponent.getBeanClassName());
                if (IDict.class.isAssignableFrom(x) && predicate.test(x.getSimpleName())) {
                    maps.put(x.getSimpleName(), toMap(((Class<? extends Enum<?>>) x)));
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return maps;
    }

    /**
     * 扫描多个枚举，以map形式返回
     * @param packages
     * @param names
     * @return
     */
    @SafeVarargs
    public static Map<String, Map<Object, String>> scanToMaps(String packages, String...names) {
        return scanToMaps(packages, Arrays.asList(names));
    }

    /**
     * 扫描单个枚举，以map形式返回
     * @param packages 包名
     * @param name 类名（非全类名）
     * @return
     */
    public static Map<Object, String> scanToMap(String packages, String name){
        Map<String, Map<Object, String>> stringMapMap = scanToMaps(packages, name);
        return stringMapMap.get(name);
    }

    /**
     * 扫描并返回多个数据字典枚举
     * @param packages 包名
     * @param names 类名（非全类名）
     * @return
     */
    public static <T>Map<String, IDict<?>[]> scanToDictEnums(String packages, String... names) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(packages);

        Map<String, IDict<?>[]> maps;
        Predicate<String> predicate;
        if (names.length == 0) {
            maps = new HashMap<>(candidateComponents.size());
            predicate = n -> true;
        } else {
            maps = new HashMap<>(names.length);
            Set<String> set = DataUtil.arrayToSet(names);
            predicate = set::contains;
        }
        try {
            for (BeanDefinition candidateComponent : candidateComponents) {
                Class<?> x = Class.forName(candidateComponent.getBeanClassName());
                if (!IDict.class.isAssignableFrom(x) || !Enum.class.isAssignableFrom(x)) {
                    continue;
                }
                DictDefine annotation = x.getAnnotation(DictDefine.class);
                if (annotation == null) {
                    continue;
                }
                String code = annotation.code();
                code = "".equals(code) ? x.getSimpleName() : code;
                if (predicate.test(code)) {
                    maps.put(code, getEnumConstants((Class<IDict>) x));
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return maps;
    }

    /**
     * 扫描并返回单个数据字典枚举
     * @param packages 包名
     * @param name 类名（非全类名）
     * @return
     */
    public static IDict[] scanToDictEnum(String packages, String name) {
        return scanToDictEnums(packages, name).computeIfAbsent(name, k -> new IDict[0]);
    }

    /**
     * 调用枚举的 valueOf 方法获取枚举
     * @param eClass
     * @param value
     * @return
     * @param <E>
     */
    public static Enum<?> invokeValueOf(Class<?> eClass, String value) {
        try {
            // 调用枚举原生方法获取
            MethodType methodType = MethodType.methodType(eClass, String.class);
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodHandle methodHandle = lookup.findStatic(eClass, "valueOf", methodType);
            return (Enum<?>)methodHandle.invoke(value);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取枚举
     * @param eClass 枚举类型
     * @param predicate 条件
     * @return
     * @param <E>
     */
    public static <E extends Enum<E>>E valueOf(Class<E> eClass, Predicate<E> predicate) {
        E[] enumConstants = getEnumConstants(eClass);
        for (E enumConstant : enumConstants) {
            if (predicate.test(enumConstant)) {
                return enumConstant;
            }
        }
        return null;
    }

    /**
     * 根据值获取数据字典枚举
     * @param eClass
     * @param value
     * @return
     * @param <E>
     */
    public static <E extends IDict<?>>E valueOf(Class<E> eClass, Object value) {
        if (value == null || !Enum.class.isAssignableFrom(eClass)) {
            return null;
        }
        E[] enumConstants = getEnumConstants(eClass);
        String string = value.toString();
        for (E enumConstant : enumConstants) {
            if (enumConstant.getValue().toString().equals(string) || enumConstant.getLabel().equals(string)) {
                return enumConstant;
            }
        }
        return (E) invokeValueOf((Class<? extends Enum<?>>)eClass, string);
    }

    private final static Map<Class<?>, Object[]> CLASS_MAP = new ConcurrentReferenceHashMap<>(256);
    /**
     * 获取枚举实例数组
     * @param eClass
     * @return
     * @param <E>
     */
    private static <E>E[] getEnumConstants(Class<E> eClass) {
        return (E[]) CLASS_MAP.computeIfAbsent(eClass, e -> {
            if (Enum.class.isAssignableFrom(e)) {
                return e.getEnumConstants();
            }
            throw new RuntimeException("必须是枚举值类型");
        });
    }

}
